#include <utl/vector.h>
#include <utl/async.h>
#include <queue>
#include <iostream>
#include <mutex>
#include <condition_variable>

#ifdef _WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // !WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif // _WINDOWS
#include <mimalloc.h>

namespace utl
{
	struct HEAP_DELETER
	{
		void operator()(mi_heap_t* ptr)const noexcept
		{
			mi_heap_destroy(ptr);
		}
	};

	using heap_ptr = eastl::unique_ptr<mi_heap_t, HEAP_DELETER>;

	static heap_ptr& get_heap_ptr()
	{
		static heap_ptr hp{ mi_heap_new() };
		return hp;
	}

	//!!! Global Thread Count
	constexpr u32 thread_max_count{ 16u };

	struct thread_context
	{
		bool life;
#ifdef __GNUC__
		int thread_id;
		pthread_t thread_handle;
#endif // __GNUC__

#ifdef _WINDOWS
		DWORD thread_id;
		HANDLE thread_handle;
#endif // __MSVC__

	};

	struct async_task_info
	{
		async_task_info(task_base* t, task_completed_pool* _tcp, bool taf) :task{ t }, tcp{ _tcp }, task_auto_free{ taf }
		{}
		task_base* task;
		task_completed_pool* tcp;
		bool task_auto_free;
	};

	struct
	{
		utl::queue<async_task_info> task_queue{};
		std::mutex queue_lock{};
		std::condition_variable queue_cv;
		thread_context context[thread_max_count]{};
		bool is_init{ false };
		mi_heap_t* m_heap;
	}global{};


#ifdef __GNUC__
	void* child_main(void* par)
#endif // __GNUC__
#ifdef _WINDOWS
	DWORD child_main(LPVOID par)
#endif // _WINDOWS
	{
		const auto ctx = reinterpret_cast<thread_context*>(par);

		while (ctx->life)
		{

			std::unique_lock ulk{ global.queue_lock };
			while (ctx->life && global.task_queue.empty())
			{
				global.queue_cv.wait(ulk);
			}

			if (!ctx->life)
			{
#ifdef __GNUC__
				return par;
#endif // __GNUC__
#ifdef _WINDOWS
				return 0;
#endif // _WINDOWS
			}


			auto t = global.task_queue.front();
			global.task_queue.pop();
			ulk.unlock();
			t.task->action(&ctx->life);
			if (t.tcp != nullptr)
			{
				t.task->state = TASK_STATE::TASK_STATE_COMPLETED;
				t.tcp->push(t.task);
			}
			else
			{
				t.task->state = TASK_STATE::TASK_STATE_NONE;
				if (t.task_auto_free)
				{
					t.task->~task_base();
					free_task_element(t.task);
				}
			}
		}
#ifdef __GNUC__
		return par;
#endif // __GNUC__
#ifdef _WINDOWS
		return 0;
#endif // _WINDOWS
	}

	void* alloc_task_element(u32 size)
	{
		return mi_heap_malloc(get_heap_ptr().get(), size);
	}

	void free_task_element(void* _ptr)
	{
		mi_free(_ptr);
	}

	void async(task_base* t, task_completed_pool* cp, bool auto_free)
	{
		std::lock_guard l{ global.queue_lock };
		t->state = TASK_STATE::TASK_STATE_ACTION;
		global.task_queue.push({ t,cp,auto_free });
		global.queue_cv.notify_all();
	}

	bool async_initialize()
	{
		if (global.is_init)
		{
			return global.is_init;
		}

		global.m_heap = mi_heap_new();
#ifdef __GNUC__
		for (auto& current_context : global.context)
		{
			current_context.life = true;
			current_context.thread_id = pthread_create(&current_context.thread_handle, nullptr, child_main, &current_context);
		}
#endif // __GNUC__
#ifdef _WINDOWS

		for (auto& current_context : global.context)
		{
			current_context.life = true;
			current_context.thread_handle = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)child_main, &current_context, CREATE_SUSPENDED, &current_context.thread_id);

		}

		for (auto& current_context : global.context)
		{
			ResumeThread(current_context.thread_handle);
		}
#endif // _WINDOWS

		global.is_init = true;
		return global.is_init;

	}

	void async_shutdown()
	{
#ifdef __GNUC__
		utl::vector<pthread_t> temp_handle;
		for (auto& item : global.context)
		{
			item.life = false;
			temp_handle.push_back(item.thread_handle);
		}

		global.queue_cv.notify_all();

		for (auto& item : temp_handle)
		{
			pthread_join(item,/*&cxt*/nullptr);
		}
#endif // __GNUC__
#ifdef _WINDOWS
		utl::vector<HANDLE> temp_handle;
		for (auto& item : global.context)
		{
			item.life = false;
			temp_handle.push_back(item.thread_handle);
		}

		global.queue_cv.notify_all();

		WaitForMultipleObjects((DWORD)temp_handle.size(), temp_handle.data(), TRUE, INFINITE);

		for (auto& item : global.context)
		{
			CloseHandle(item.thread_handle);
		}
#endif // _WINDOWS
		mi_heap_destroy(global.m_heap);
		global.m_heap = nullptr;

		global.is_init = false;
	}

	void task_completed_pool::push(task_base* t, bool af)
	{
		std::lock_guard l{ locker };
		this->completed_queue.push({ t,af });
	}

	void task_completed_pool::do_thing(u32 task_count)
	{
		if (!completed_queue.empty())
		{
			std::lock_guard l{ locker };

			for (u32 i{ 0u }; i < task_count && !this->completed_queue.empty(); i++)
			{
				auto tsk{ this->completed_queue.front().task };
				bool af{ this->completed_queue.front().auto_free };
				this->completed_queue.pop();
				tsk->completed();
				tsk->state = TASK_STATE::TASK_STATE_NONE;
				//tsk->on_completed();
				if (af)
				{
					tsk->~task_base();
					free_task_element(tsk);
				}
			}
		}
	}

	task_base::~task_base()
	{
		assert(state == TASK_STATE::TASK_STATE_NONE);
	}

	//void task_base::on_completed()
	//{
	//	this->cv.notify_all();
	//}

	void task_base::run(task_completed_pool* tcp)
	{
		async(this, tcp, false);
	}

	//void task_base::wait()
	//{
	//	std::unique_lock ul{ this->locked };
	//	this->cv.wait(ul, [this]()
	//	{
	//		return this->state == TASK_STATE::TASK_STATE_NONE;
	//	});
	//}
	
}